/**
 * 
 */
package com.feib.stms.action;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.WebAttributes;

import com.feib.stms.model.User;
import com.feib.stms.security.web.authentication.ReLoginAuthenticationException;
import com.feib.stms.security.web.authentication.StmsAuthenticationFailureHandler;
import com.feib.stms.service.UserService;
import com.feib.stms.util.UserUtil;

import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.DontBind;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.integration.spring.SpringBean;

/**
 * @title (#)Soez9011ActionBean.java<br>
 * @description 登入密碼修改 <br>
 * @author Jimmy Liu<br>
 * @version 1.0.0 2010/11/25
 * @copyright Far Eastern International Bank Copyright (c) 2010<br>
 * @2010/12/15 create by Jimmy Liu<br>
 */
public class Stms9001ActionBean  extends BaseActionBean 
{
	private static final long serialVersionUID = 7947008872389414076L;
	

    // 規則 密碼長度 8-20位 、至少1位英數、至少1位數字
    private static String PWD_RULE = "^(?=(.*\\d){1,})(?=(.*[A-Za-z]){1,}).{8,20}$";
    

    // 連號字串 比對 0-9 9-0 A-Z a-z Z-A z-a
    private static String SEQUENCE_CHARS[] = {"0123456789", "9876543210"
        , "abcdefghijklmnopqrstuvwxyz", "zyxwvutsrqponmlkjihgfedcba"
        , "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ZYXWVUTSRQPONMLKJIHGFEDCBA"};
    
    // 連續數字或是英文字母的長度限制 
    private static int SEQUENCE_LENGTH = 5;
    
    // 相同字元的長度限制
    private static int SAME_CHAR_LENGTH = 5;
    
    // 密碼不可與前幾次相同
    private static int PWD_HISTORY_TIMES = 3;
    
    // 密碼有效期限 (月)
    private static int PWD_EXPIRATION = 3; // 3個月

    private static final String DEFAULT_JSP = "/WEB-INF/views/stms9001/stms9001.jsp";
    
    /**
     * 原本密碼
     */
    private String currentPwd;
    
    /**
     * 新密碼
     */
    private String newPwd;
    
    /**
     * 確認密碼
     */
    private String confirmPwd;
    
	/**
	 * 訊息-畫面顯示用
	 */
	protected String message;

	/**
	 * 錯誤訊息-畫面顯示用
	 */
	protected String errorMessage;
	
    
    private User dbUser;
    
    private List<String> errors = new ArrayList<String>();
    
    @SpringBean(value="stmsPasswordEncoder")
    private Md5PasswordEncoder md5PasswordEncoder;

    @SpringBean(value="userService")
    private UserService userService;
    
    @SpringBean(value="stmsAuthenticationEntryPoint")
    private AuthenticationEntryPoint stmsAuthenticationEntryPoint;
    
    @DontBind
    @DefaultHandler
    public Resolution doDefault()
    {
        logger.debug("Enter doDefault mehtod.");
        
        try {
        	if (isForceChgPwd())
        		this.errorMessage = "密碼過期，請先變更密碼！";
		} catch (Exception e) {
			// skip
		}
        return new ForwardResolution(DEFAULT_JSP); 
    }
    
    public Resolution doChangePwd()
    {
        logger.debug("Enter doChangePwd mehtod.");
        try
        {
            validatePassword();
            
            if (0 < errors.size())
            {
                // 檢核有誤
            	errorMessage = this.converErrorsToString();
            }
            else
            {

            	dbUser.setUpdatedDate(new Date());
            	dbUser.setUpdatedBy(dbUser.getGroup().getGroupNo() +"_" + dbUser.getUserId());
                userService.changePwdSuccess(dbUser);
                if (null != UserUtil.getUser())
                    message = "密碼變更成功！";
                else
                {
                    message = "密碼變更成功！請重新登入";

                    this.getContext().getRequest().setAttribute(StmsAuthenticationFailureHandler.LAST_STMS_FAIL_AUTHENTICATION_USERID, dbUser.getUserId());
                    this.getContext().getRequest().setAttribute(StmsAuthenticationFailureHandler.LAST_STMS_FAIL_AUTHENTICATION_GROUPNO, dbUser.getGroup().getGroupNo());
                    
                    this.getContext().getRequest().getSession().setAttribute(StmsAuthenticationFailureHandler.LAST_STMS_FAIL_AUTHENTICATION_USERID, dbUser.getUserId());
                    this.getContext().getRequest().getSession().setAttribute(StmsAuthenticationFailureHandler.LAST_STMS_FAIL_AUTHENTICATION_GROUPNO, dbUser.getGroup().getGroupNo());


                    /* 清空 spring security exception*/
                    SecurityContextHolder.clearContext();
                    this.getContext().getRequest().removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
                    this.getContext().getRequest().getSession().removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
                    
                    
                    ReLoginAuthenticationException authException = new ReLoginAuthenticationException(message); 
                    this.getContext().getRequest().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, authException);
                    this.getContext().getRequest().getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, authException);
                    
                    /* 再導致 lgoin 登入畫面 */
                    stmsAuthenticationEntryPoint.commence(this.getContext().getRequest(), this.getContext().getResponse(), authException);
                    return null;
                }
            }
            
        }
        catch(Exception e)
        {
            logger.error("變更密碼發生錯誤：", e);
            errorMessage = "密碼變更失敗！";
        }
        return new ForwardResolution(DEFAULT_JSP);
    }
    
    public void validatePassword() {
        
        if (StringUtils.isEmpty(this.currentPwd))
        {
            // 原始密碼未輸入
            errors.add("目前密碼未輸入");
        }
        
        if (StringUtils.isEmpty(this.newPwd))
        {
            // 新密碼未輸入
            errors.add("新密碼未輸入");
        }
        
        if (StringUtils.isEmpty(this.confirmPwd))
        {
            // 確認密碼未輸入
            errors.add("確認新密碼未輸入");
        }
        
        
        if (0 < errors.size())
            return ;
        
        if (! this.newPwd.equals(this.confirmPwd))
        {
            // 新密碼與確認密碼不符
            errors.add("新密碼與確認新密碼不符");
            
            return;
        }
        
        if (! checkCharRule(this.newPwd))
        {
            // 密碼長度 8-20位 、至少1位英數、至少1位數字 不通過
            errors.add("密碼長度 8-20位 、至少1位英數、至少1位數字");
            
            return ;
        }
        
        if(! checkSequence(this.newPwd))
        {
            // 密碼不可連續數字或是英文字母
            errors.add("密碼不可連續數字或是英文字母");
            
            return ;
        }
        
        if(! checkSameChar(this.newPwd))
        {
            // 相同字元的長度限制
            errors.add("密碼不可連續有相同數字或是英文字母");
            
            return ;
        }
        
        User user = this.getUser();
        if (null == user)
        {
            // 尚未登入
            errors.add("請先登入");
            
            return ;
        }
        
        dbUser = this.userService.findByUerPk(user.getUserPk());
        
        String currentPwdMd5 = md5PasswordEncoder.encodePassword(currentPwd, null);
        String newPwdPwdMd5 = md5PasswordEncoder.encodePassword(newPwd, null);
        
        if (! dbUser.getUserPwd().equals(currentPwdMd5))
        {
            // 原本密碼與資料庫中的密碼不相同
            errors.add("目前密碼輸入錯誤");
            
            return ; 
        }
        
        // 檢核密碼不可跟前N次相同
        
        String pwdHistory = dbUser.getPwdHistory();
        List<String> historys = new ArrayList<String>();
        if (null != pwdHistory)
        {
            for(String s : StringUtils.split(pwdHistory, ","))                
            {
                historys.add(s);
            }          
        }
        
        historys.add(dbUser.getUserPwd());
 
        for(String history: historys)
        {
            if (history.equals(newPwdPwdMd5))
            {
                // 密碼與前N次相同
                errors.add("新密碼不可跟前 "+PWD_HISTORY_TIMES+" 次密碼相同");
                return;
            }
        }
        
        
        // 待檢核完成 超過次數就移除舊的
        if (historys.size() > PWD_HISTORY_TIMES)
        {
            historys.remove(0);
        }

        
        //  Yes ! 檢核通過
        StringBuilder sb = new StringBuilder();
        for (String x : historys)
            sb.append(x + ",");
        sb.delete(sb.length() - 1, sb.length());
        
        dbUser.setPwdHistory(sb.toString());
        dbUser.setUserPwd(newPwdPwdMd5);
        
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.MONTH, PWD_EXPIRATION); // 3個月後到期
        dbUser.setPwdExpiredDate(cal.getTime());
        dbUser.setPwdErrorTime(new Integer(0));       
    }
    
    public User getUser()
    {
        User user = UserUtil.getUser();
        
        if (null == user) // 強迫變更密碼進來的
            user = (User)this.getContext().getRequest().getSession().getAttribute("__FORECE_CHG_PWD_USER");
        
        return user;
    }
    
    private String converErrorsToString()
    {
        StringBuffer sb = new StringBuffer();
        
        sb.append("<div id=\"validationErrorDiv\">");
        for (String error : errors) {
            sb.append("<div class=\"error\">");
            sb.append(error);
            sb.append("</div>");
        }
        sb.append("</div>");
        
        return sb.toString();
    }
    
    /**
     * 相同字元的長度限制
     * @param input
     * @return
     */
    private boolean checkSameChar(String input){
        boolean result=true;

        for(int i=0; i < (input.length()- SAME_CHAR_LENGTH + 1); i++){
                char aryCheckChar[]=input.substring(i, i + SAME_CHAR_LENGTH).toCharArray();
                //check 是否相同字元
                char m_cFirstChar=aryCheckChar[0];
                boolean m_bFlagSameChar = true;
                for(int j=0; j < aryCheckChar.length; j++){
                        if(m_cFirstChar != aryCheckChar[j]){
                                m_bFlagSameChar = false;
                        }
                }
                if(m_bFlagSameChar){    
                        return false;
                }
        }
        return result;
}
    
    
    /**
     * 檢核密碼不可連續數字或是英文字母
     * @param input 密碼
     * @return
     */
    private boolean checkSequence(String input) {
        boolean result=true;

        for(int i=0; i < (input.length()- SEQUENCE_LENGTH + 1); i++){
                String checkString=input.substring(i, i + SEQUENCE_LENGTH);
                //check 是否連號
                for(int j=0; j < SEQUENCE_CHARS.length; j++){
                        if(SEQUENCE_CHARS[j].indexOf(checkString)!= -1){                                       
                                return false;
                        }
                }
        }
        return result;
}

    /**
     * 檢核密碼長度 8-20位 、至少1位英數、至少1位數字
     * @param input
     * @return
     */
    private boolean checkCharRule(String input){
        boolean result = false;
        Pattern p = Pattern.compile(PWD_RULE);
        Matcher m = p.matcher(input);
        result = m.find();
        return result;
    }

    
    /**
     * @return the currentPwd
     */
    public String getCurrentPwd() {
        return currentPwd;
    }

    
    /**
     * @param currentPwd the currentPwd to set
     */
    public void setCurrentPwd(String currentPwd) {
        this.currentPwd = currentPwd;
    }

    
    /**
     * @return the newPwd
     */
    public String getNewPwd() {
        return newPwd;
    }

    
    /**
     * @param newPwd the newPwd to set
     */
    public void setNewPwd(String newPwd) {
        this.newPwd = newPwd;
    }

    
    /**
     * @return the confirmPwd
     */
    public String getConfirmPwd() {
        return confirmPwd;
    }

    
    /**
     * @param confirmPwd the confirmPwd to set
     */
    public void setConfirmPwd(String confirmPwd) {
        this.confirmPwd = confirmPwd;
    }

    
    /**
     * @return the message
     */
    public String getMessage() {
        return message;
    }

    
    /**
     * @param message the message to set
     */
    public void setMessage(String message) {
        this.message = message;
    }

    /**
     * 強迫變更註記
     */
	public boolean isForceChgPwd() {
		return (null == UserUtil.getUser());
	}

	public String getErrorMessage() {
		return errorMessage;
	}

	public void setErrorMessage(String errorMessage) {
		this.errorMessage = errorMessage;
	}

}
